#pragma once
#include "GameSystem.hpp"
#include "Transform.hpp"
#include "Renderable.hpp"
#include <vector>
#include <map>
#include <set>
#include "RenderCall.hpp"
#include "Camera.hpp"
#include "CellGrid.hpp"
#include <sstream>
#include "LightSystem.hpp"

namespace Typhoon {
	template<class T = Renderable>
	class RenderSystem : public GameSystem
	{
	public:
		RenderSystem();
		~RenderSystem();

		void Render(GameObject* camera, const Box& viewport);
				
	protected:

		void GameObjectAdded(GameObject* gameObject);
		void GameObjectRemoved(GameObject* gameObject);

	private:

		struct Visualizer {
			Mesh* Mesh;
			Texture* Texture;
			Box BoundingBox;
		};

		Box viewport;
		GameObject* camera;

		void TransformChanged(DirtyProperty<Transform*, Matrix4x4>* dirtyProperty);

		void GridInsertion(Transform* transform);

		void CheckVisiblity(Transform* transform);

		void OrderChanged(Renderable* meshVisualizer);
		
		typedef std::vector<T*> VisibleObjects;
		VisibleObjects visibleObjects;

		typedef std::set<Transform*> VisibilityChecks;
		VisibilityChecks visibilityChecks;

		typedef std::set<Transform*> InsertionChecks;
		InsertionChecks insertionChecks;
		
		typedef std::map<T*, Visualizer> VisualizerMap;
		VisualizerMap visualizerMap;
		
		typedef std::vector<RenderCall> RenderCalls;

		int renderCallsIndex;
		RenderCalls renderCalls;
		RenderCall* currentRenderCall;
		
		void RenderGameObject(T* meshVisualizer);

		bool sortVisibleObjects;

		CellGrid<Transform*> grid;
		
		static bool SortMethod_(T* a, T* b);

		LightSystem lightSystem;

	};
}

template<class T>
Typhoon::RenderSystem<T>::RenderSystem()
{
	AddComponent<Transform>();
	AddComponent<T>();

	currentRenderCall = new RenderCall();
	renderCalls.push_back(*currentRenderCall);

	sortVisibleObjects = false;

	grid.SetSize(Box(-1000,-1000,1000,1000));

	subSystems.push_back(&lightSystem);
}

template<class T>
Typhoon::RenderSystem<T>::~RenderSystem()
{
}

template<class T>
void Typhoon::RenderSystem<T>::GameObjectAdded( GameObject* gameObject )
{
	T* meshVisualizer = gameObject->GetComponent<T>();
	Visualizer* visualizer = new Visualizer();
	
	AddData(meshVisualizer, visualizer);
	Transform* transform = gameObject->GetComponent<Transform>();
	transform->World.HasBecomeDirty += event_handler(this, &Typhoon::RenderSystem<T>::TransformChanged);
	transform->World.MakeDirty();
}

template<class T>
void Typhoon::RenderSystem<T>::GameObjectRemoved( GameObject* gameObject )
{
	T* meshVisualizer = gameObject->GetComponent<T>();

	Visualizer* visualizer = (Visualizer*)GetData(meshVisualizer);
	delete visualizer;

	gameObject->GetComponent<T>()->Order.Changed -= event_handler(this, &Typhoon::RenderSystem<T>::OrderChanged);
	gameObject->GetComponent<Transform>()->World.HasBecomeDirty -= event_handler(this, &Typhoon::RenderSystem<T>::TransformChanged);
}

template<class T>
void Typhoon::RenderSystem<T>::TransformChanged(DirtyProperty<Transform*, Matrix4x4>* dirtyProperty) {
	insertionChecks.insert(dirtyProperty->GetOwner());
}

template<class T>
void Typhoon::RenderSystem<T>::OrderChanged(Renderable* meshVisualizer) {
	sortVisibleObjects = true;
}

template<class T>
void Typhoon::RenderSystem<T>::CheckVisiblity( Transform* transform )
{
	T* meshVisualizer = transform->gameObject->GetComponent<T>();
    Visualizer* visualizer = (Visualizer*)GetData(meshVisualizer);
	bool isVisible = viewport.Intersect(visualizer->BoundingBox);
	
	if (isVisible) {
		visibleObjects.push_back(meshVisualizer);
		sortVisibleObjects = true;
	}

	return;
	/*
	if (isVisible!=visualizer.IsVisible) {
		visualizer.IsVisible = isVisible;
		if (isVisible) {
			meshVisualizer->Order.Changed += event_handler(this, &Typhoon::RenderSystem<T>::OrderChanged);
			visibleObjects.push_back(meshVisualizer);
			sortVisibleObjects = true;
		}
		else {
			meshVisualizer->Order.Changed -= event_handler(this, &Typhoon::RenderSystem<T>::OrderChanged);
			//visibleObjects.remove(meshVisualizer);
		}
	}
	*/
}

template<class T>
bool Typhoon::RenderSystem<T>::SortMethod_( T* a, T* b )
{
	return a->Order.GetValue()<b->Order.GetValue();
}

template<class T>
void Typhoon::RenderSystem<T>::GridInsertion(Transform* transform) {
	/*
	T* renderable = transform->gameObject->GetComponent<T>();

	Mesh* mesh = renderable->Mesh;
	if (!mesh) return;

	const Matrix3x3& t = transform->Transform.GetValue();

	Box boundingBox = mesh->BoundingBox;

	boundingBox.FromMatrix(t);

	Visualizer* visualizer = (Visualizer*)GetData(meshVisualizer);

	grid.Remove(transform, visualizer->BoundingBox);
	grid.Add(transform, boundingBox);
	visualizer->BoundingBox = boundingBox;
	*/
}

template<class T>
void Typhoon::RenderSystem<T>::Render(GameObject* camera, const Box& viewport) {

	this->camera = camera;
	this->viewport = viewport;

	

	/*
	if (!insertionChecks.empty()) {
		for (InsertionChecks::iterator it=insertionChecks.begin(); it!=insertionChecks.end(); ++it) {
			GridInsertion(*it);
		}
		insertionChecks.clear();
	}


	visibleObjects.clear();
	int counter = 0;
	grid.StartIteration(this->viewport);
	Transform* transform;
	while(grid.GetNext(transform)) {
		CheckVisiblity(transform);
		counter++;
	}
	*/
	

	visibleObjects.clear();
	GameObjects& objs = GetGameObjects();
	for	(GameObjects::iterator it = objs.begin(); it!=objs.end(); ++it) {
		visibleObjects.push_back((*it)->GetComponent<Renderable>());
	}
	
	if (sortVisibleObjects) {
		sortVisibleObjects = false;
		//visibleObjects.sort(SortMethod_);
		std::sort(visibleObjects.begin(), visibleObjects.end(), SortMethod_);
	}
	

	renderCallsIndex = 0;
	currentRenderCall = &renderCalls[0];
	currentRenderCall->Reset();
	int j=visibleObjects.size();
	for (int i=0; i<j; i++) {
		RenderGameObject(visibleObjects[i]);
	}
	//for (VisibleObjects::iterator it=visibleObjects.begin(); it!=visibleObjects.end(); ++it) {
	//	RenderGameObject(*it);
	//}

	

	//std::stringstream ss;
	//ss<<"Visible meshes : " <<visibleObjects.size()<<"\n";

	//OutputDebugStringA(ss.str().c_str());


	if (camera) {

		Matrix4x4& projection = *camera->GetComponent<Camera>()->Projection.GetValue();

		glMatrixMode(GL_PROJECTION); 
		glLoadIdentity(); 
		glLoadMatrixf(projection.GetGlMatrix());

		Matrix4x4& world = *camera->GetComponent<Transform>()->WorldInverse.GetValue();

		Matrix4x4 worldGl = world.Transpose();

		glMatrixMode (GL_MODELVIEW); 
		glLoadIdentity();
		glMultMatrixf(worldGl.GetGlMatrix());

	}

	lightSystem.Render(camera, viewport);

	glEnable(GL_TEXTURE_2D);
	glEnable (GL_BLEND); 
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	//glDisable(GL_DEPTH_TEST);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glFrontFace(GL_CCW);
	//glEnable(GL_LIGHTING);
	glEnable(GL_COLOR_MATERIAL);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);

	for (int i=0; i<=renderCallsIndex; i++) {
		renderCalls[i].Render();
	}

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisable(GL_TEXTURE_2D);
	glDisable (GL_BLEND); 

	//if (camera)
	//	camera->End();
}

template<class T>
void Typhoon::RenderSystem<T>::RenderGameObject( T* meshVisualizer )
{
	const Matrix4x4& world = *meshVisualizer->gameObject->template GetComponent<Transform>()->World.GetValue();
	Mesh& mesh = *meshVisualizer->Mesh.GetValue();
	Texture* texture = meshVisualizer->Texture.GetValue();

	if (!currentRenderCall->RenderSprite(world, mesh, texture)) {
		renderCallsIndex++;
		if (renderCallsIndex>=renderCalls.size()) {
			currentRenderCall = new RenderCall();
			renderCalls.push_back(*currentRenderCall);
		} else {
			currentRenderCall = &renderCalls[renderCallsIndex];
		}
		currentRenderCall->Reset();
		currentRenderCall->RenderSprite(world, mesh, texture);
	}
}


